#include "impltcpsocket.h"
//#pragma comment (lib, "ws2_32.lib")

using Lock = std::unique_lock<std::mutex>;

TcpSocket::Impl::Impl()
    : socket_(INVALID_SOCKET)
    , connFlag_(false)
    , readyRead_(false)
    , bufferSize_(0)
    , dataSize_(0)
    , about_(false)
{
    socket_ = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    setBufferSize(1024);
    fnReadyRead_ = [](){};
    th_ = std::thread(
        [this] {
            while(!about_) {
                if(!connFlag_) {
                    std::this_thread::yield();
                    continue;
                }
                if(!_isReadyRead()) continue;
                Lock lock(mtx_);
                if(bufferSize_ == dataSize_) continue;
                int len = ::recv(socket_, buffer_.get()+dataSize_, bufferSize_-dataSize_, 0);
                if(len<=0) continue;
                dataSize_ += len;
                readyRead_ = true;
                lock.unlock();
                fnReadyRead_();
                cv_.notify_one();
            }
        }
    );
}

TcpSocket::Impl::~Impl()
{
    ::shutdown(socket_, SD_BOTH);
    ::closesocket(socket_);
    about_ = true;
    th_.join();
}

int TcpSocket::Impl::startup()
{
    WSADATA wsaDate = {};
    return ::WSAStartup(MAKEWORD(2, 2), &wsaDate);
}

int TcpSocket::Impl::cleanup()
{
    return ::WSACleanup();
}

void TcpSocket::Impl::setBufferSize(unsigned int size)
{
    if(size == 0 || size == bufferSize_) return;
    Lock lock(mtx_);
    char *tmp = nullptr;
    try { tmp = new char[size]{0}; }
    catch(...) { return; }
    if(dataSize_>0) {
       if(size > dataSize_) memcpy(tmp, buffer_.get(), dataSize_);
       else memcpy(tmp, buffer_.get()+dataSize_-size, size);
    }
    buffer_.reset(tmp);
    bufferSize_ = size;
}

unsigned int TcpSocket::Impl::readBufferSize() const
{
    return bufferSize_;
}

bool TcpSocket::Impl::connectToHost(std::string host, unsigned short port)
{
    abort();
    if(socket_ == INVALID_SOCKET) {
        socket_ = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if(socket_ == INVALID_SOCKET) return false;
    }

    sockaddr_in in = {};
    in.sin_family = PF_INET;
    in.sin_addr.S_un.S_addr = ::inet_addr(host.c_str());
    in.sin_port = ::htons(port);

    int ret = ::connect(socket_, (SOCKADDR*)&in, sizeof(in));
    if(ret == SOCKET_ERROR) { // timeout or error
        return false;
    }
    connFlag_ = true;
    return true;
}

void TcpSocket::Impl::abort()
{
    if(!connFlag_) return;
    connFlag_ = false;
    dataSize_ = 0;
    ::shutdown(socket_, SD_BOTH);
}

int TcpSocket::Impl::write(const char *data, int len)
{
    return ::send(socket_, data, len, 0);
}

unsigned int TcpSocket::Impl::read(char *data, unsigned int maxlen)
{
    if(!data || !maxlen || !isReadyRead()) return 0;
    Lock lock(mtx_);
    char* buf = buffer_.get();
    unsigned int dataSize = dataSize_;
    unsigned int len = maxlen < dataSize ? maxlen : dataSize;
    memcpy(data, buf, len);

    dataSize_ -= len;
    if(dataSize_) memcpy(buf, buf+len, dataSize_);
    else readyRead_ = false;

    return len;
}

std::string TcpSocket::Impl::read(unsigned int maxlen)
{
    if(!maxlen || !isReadyRead()) return "";
    Lock lock(mtx_);
    char* buf = buffer_.get();
    unsigned int dataSize = dataSize_;
    unsigned int len = maxlen < dataSize ? maxlen : dataSize;
    std::string data(buf, len);

    dataSize_ -= len;
    if(dataSize_) memcpy(buf, buf+len, dataSize_);
    else readyRead_ = false;

    return data;
}

bool TcpSocket::Impl::waitForReadyRead(int msecs)
{
    if(msecs < 0) {
       Lock lock(mtx_);
       cv_.wait(lock);
    }
    else if(msecs > 0) {
       Lock lock(mtx_);
       cv_.wait_for(lock, std::chrono::milliseconds(msecs));
    }
    return readyRead_;
}

void TcpSocket::Impl::registerReadyReadSig(std::function<void ()> callback)
{
    Lock lock(mtx_);
    fnReadyRead_ = callback;
}

bool TcpSocket::Impl::_isReadyRead()
{
    if(!connFlag_) return false;
    fd_set fdSocket;
    FD_ZERO(&fdSocket);
    FD_SET(socket_, &fdSocket);
    fd_set fdRead = fdSocket; //
    timeval time;
    memset(&time, 0, sizeof(time));
    int ret = ::select(0, &fdRead, NULL, NULL, /*&time*/nullptr);
    if (ret <= 0) return false; // -1:error 0:timeout >0:succeed
    if (!FD_ISSET(fdSocket.fd_array[0], &fdRead)) return false; // socket unreadable (disconnect)
    char ch{0};
    int result = ::recv(socket_, &ch, 1, MSG_PEEK);
    if(result == 0 || result == SOCKET_ERROR) { // 0:remote close
        connFlag_ = false;
        return false;
    }
    return true; // can read
}
